#!/usr/bin/perl -w

# This script parses the input options file passed as $ARGV[0] and
# generates an $prefix-options.h and $prefix-options.C file in the current
# directory where the input options file must be $preifix.default.options.
# The input options file is a collection of "dom::field value" lines.  For
# each option dom::field, this script creates a variable __dom__field.
# User calls load_options_from_file(options_file) to set these variables
# to the value specified in options_file.

eval 'exec perl -w -S $0 ${1+"$@"}'
    if 0;

use strict;

my $default_opt_file = shift @ARGV;
my ($prefix) = $default_opt_file =~ /^(.*)\.default\.options$/;
my $note = 
    "// DO NOT EDIT -- automatically generated by $0\n".
    "// from $default_opt_file\n";
my %options = ();

sub main {

    my $hfile = "../../include/tern/tern-options.h";
    my $cfile = "$prefix-options.cpp";

    read_optf($default_opt_file, \%options);
    emit_if_diff(\%options, $cfile, \&emit_cfile);
    emit_if_diff(\%options, $hfile, \&emit_header);
}

sub read_optf($$)
{
    my ($file, $optref) = @_;
    return unless -f $file;

    open OPTF, $file || die $!;
    while (<OPTF>) {
	next if /^#/ || /^\s*$/; # skip comments

        # check for simple typo
	if(/^([^\s]+)::([^\s]+)\s*$/) {
	    warn "warning: No value specified for option $1::$2 at line $. in $file!\n";
	    exit(1);
	}
        if (/^([^\s:]+):([^\s:]+)/) {
	    warn "warning: missing : in  $1:$2 at line $. in $file!\n";
	    exit(1);
	}
        if (!/^([^\s]+)::([^\s]+)\s+([^\s]+)\s*$/) {
            warn "mal-formated option at line $. in $file: $_";
            exit(1);
        }
        my ($dom, $key, $val) = ($1, $2, $3);
        $val =~ s/^\"(.*)\"$/$1/; # strip quotes
	$optref->{$dom}{$key} = $val;
    }
    close OPTF;
}

sub emit_if_diff($$$)
{
    my ($optref, $file, $emit_fn) = @_;
    my $tmp = $file.".tmp";

    &$emit_fn ($optref, $tmp);

    if(!-f $file || `diff $file $tmp`) {
	system ("mv $tmp $file");
    } else {
	unlink "$tmp";
    }    
}

sub emit_options($$)
{
    my ($optref, $file) = @_;
    open OPT, ">$file" || die $!;
    foreach my $dom (sort keys %$optref) {
        print OPT
            join("\n",
                 map {"${dom}::${_}     $optref->{$dom}{$_}"}
                 (sort keys %{$optref->{$dom}})), "\n";
    }
    close OPT;
}

sub emit_header($$)
{
    my ($optref, $file) = @_;

    my $def = "__$prefix"."_OPTIONS_H";

    # variable declarations
    my $opt_decl = "";
    foreach my $dom (sort keys %$optref) {
        $opt_decl .=
            join("\n",
                 map {my $type = opt_type ($optref->{$dom}{$_});
                      my $name = opt_name ($dom, $_);
                      "extern $type $name;";} 
                 sort keys %{$optref->{$dom}});
        $opt_decl .= "\n";
    }


    open HEADER, ">$file" || die $!;
    print HEADER<<CODE;
$note

// stop being C compatible, to simplify things.  The entire code base 
// is in C++ now, except the kernel drivers.  Can always create a C interface 
// if need be.

#ifndef $def
#define $def

$opt_decl

#endif

CODE

    close HEADER;
}

sub emit_cfile($$)
{
    my ($optref, $file) = @_;
    my ($dom, $key);

    # ~options() function body
    my $clean_options_body = "";
    foreach my $dom (sort keys %$optref) {
        $clean_options_body .=
            join("\n",
                 map {my $type = opt_type ($optref->{$dom}{$_});
                      my $name = opt_name ($dom, $_);
                      if($type eq "const char*") {
                          "    free((void*)$name);\n\t$name = NULL;\n";
                      }} sort keys %{$optref->{$dom}});
        $clean_options_body .= "\n";
    }

    # init function body
    my $init_body = "";
    foreach my $dom (sort keys %$optref) {
        $init_body .=
            join("\n",
                 map {my $type = opt_type ($optref->{$dom}{$_});
                      my $name = opt_name ($dom, $_);
                      my $res = "";
                      if($type eq "const char*") {
                          $res = "    $name = strdup(\"$optref->{$dom}{$_}\");";
                      }
                      $res;} sort keys %{$optref->{$dom}});
        $clean_options_body .= "\n";
    }

    # load_option function body
    my $load_option_body = "";
    my $load_pat = 
'         {
             char *v = (char *)val;
             if(%N) free((void*)%N);
             if(v[0] == \'\"\') v++;
             if(v[strlen(v)-1] == \'\"\') v[strlen(v)-1]=0;
             %N = strdup(v);
             return 1;
        }';


    foreach my $dom (sort keys %$optref) {
        $load_option_body .= "    if (!strcmp (dom, \"$dom\")) {\n";
        $load_option_body .=
            join("\n",
                 map {
                     my $type = opt_type ($optref->{$dom}{$_});
                     my $name = opt_name ($dom, $_);
                     my $res = "        if (!strcmp (opt, \"$_\"))\n";
                     if($type eq "const char*") {
                         $res .= $load_pat;
                         $res =~ s/\%N/$name/g;
                     } elsif ($type eq "float") {
                         $res .= "            { $name = (float)atof(val); return 1; }";
                     } else {
                         $res .= "            { $name = (int)strtoul(val, 0, 0); return 1; }";
                     }
                     $res;} sort keys %{$optref->{$dom}});
        $load_option_body .= "\n\n    }\n";
    }

    # print_options_to_stream function body
    my $print_options_body = "";
    foreach my $dom (sort keys %$optref) {
        $print_options_body .= 
            join("\n",
                 map {my $name = opt_name ($dom, $_);
                      "    o << \"${dom}::${_}    \" << $name << endl;";} 
                 sort keys %{$optref->{$dom}});
    }

    # print variable definitions
    my $opt_def = "";
    foreach my $dom (sort keys %$optref) {
        $opt_def .=
            join("\n",
                 map {my $type = opt_type ($optref->{$dom}{$_});
                      my $name = opt_name ($dom, $_);
                      if ($type eq "const char*") {
                          "$type $name = NULL;\n";
                      } else {
                          "$type $name = $optref->{$dom}{$_};\n";
                      }} sort keys %{$optref->{$dom}});
        $opt_def .= "\n";
    }

    open CFILE, ">$file" || die $!;
    print CFILE<<CODE;

$note
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <assert.h>

#include "tern/options.h"

using namespace std;

$opt_def

namespace {

struct _options: public options {

    virtual ~_options() {
$clean_options_body
    }

    virtual void init() {
$init_body
    }

    virtual int load(const char *dom, const char *opt, const char *val) {
$load_option_body
        return 0;
    }
    
    virtual void print(std::ostream &o) {
$print_options_body        
    }
    
};

struct register_options x(new _options);

}

CODE

    close CFILE;
}

sub opt_type($)
{
    my ($value) = @_;
    if ($value =~ /^[+-]?\d+$/) {return "int";}
    elsif($value =~ /^0x[\da-fA-F]+$/) { return "unsigned";}
    #elsif ($value =~ /^[+-]?\d*\.\d+$/) {return "float";}
    else {return "const char*";}
}

sub opt_name($$)
{
    my ($dom, $key) = @_;
    return join("__", ("", $dom, $key));
}

main;
